﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Accord.Statistics.Analysis;
using TimbreRecognition.Extraction;
using TimbreRecognition.FeatureExtraction;
using TimbreRecognition.Recognition;
using TimbreRecognition.Recognition.Helper;
using TimbreRecognition.Recognition.Model.Kohenen;
using TimbreRecognition.Recognition.Teacher.Kohenen;

namespace TimbreRecognition.Desktop
{
    public partial class KohenenMapForm : Form
    {
        private const int NetworkWidth = 15;

        private const int NetworkHeight = 15;

        private const int ColorMaxValue = 255;

        private const int AnyClassIndex = -1;

        private readonly string sourceDirectory;

        private readonly IFeatureExtractor featureExtractor;

        private FeatureNormalizer featureNormalizer;

        private FeatureExtractionFacade featureFacade;

        private KohenenNetwork network;

        private List<double[]> mfcc;

        private Graphics mapGraphics;

        public KohenenMapForm(string sourceDirectory, string[] instruments)
        {        
            InitializeComponent();

            this.sourceDirectory = sourceDirectory;

            featureExtractor = new FeatureExtractor(FeatureConstants.NumberOfCoeficients, instruments);

            featureNormalizer = FeatureNormalizer.GetInstance();

            this.classesListBox.Items.AddRange(instruments);

            mapGraphics = mapPanel.CreateGraphics();

            featureFacade = new FeatureExtractionFacade(instruments);
        }

        private void trainButton_Click(object sender, EventArgs e)
        {

            List<DataItem> trainingData = featureFacade.ExtractForClassification(sourceDirectory);

            IKohenenTeacher iterationTeacher = KohenenTeacherFactory.createTeacher(KohenenTeachers.ITERATION);
            iterationTeacher.SetLogger(new TextBoxLogger(this.logTextBox));

            List<double[]> data = trainingData.Select(o => o.DataSeries).ToList();

            double[][] sourceMatrix = new double[data.Count][];
            for (int i = 0; i < data.Count; i++)
            {
                double[] sample = data[i];
                sourceMatrix[i] = sample;
            }

            const int numberOfComponents = 4;

            // Creates the Principal Component Analysis of the given source
            PrincipalComponentAnalysis pca = new PrincipalComponentAnalysis(sourceMatrix, AnalysisMethod.Standardize);


            // Compute the Principal Component Analysis
            pca.Compute();

            // Creates a projection considering 80% of the information
            double[][] principleComponents = pca.Transform(sourceMatrix, numberOfComponents);

            List<double[]> pcaData = data; // principleComponents.ToList(); 

            network = NeuronNetworkFactory.createKonenanNetwork(NetworkWidth, NetworkHeight, pcaData[0].Length);

            iterationTeacher.teach(network, pcaData);

            List<DataItem> dataItems = new List<DataItem>();
            for (int i = 0; i < pcaData.Count; i++)
            {
                DataItem dataItem = new DataItem()
                {
                    DataSeries = pcaData[i],
                    ExpectedOutput = trainingData[i].ExpectedOutput
                };
                dataItems.Add(dataItem);
            }

            NeuronNetworkHelper.CalculeteWinCountForEachClass(network, dataItems);

            ShowResult(network, AnyClassIndex, checkBox.Checked);  

            MessageBox.Show("Done");
        }

        private void ShowResult(KohenenNetwork network, int classIndex, bool normalizationWithinClass)
        {
            mapGraphics.Clear(mapPanel.BackColor);

            int regtangleWidth = this.mapPanel.Width / network.Width;
            int regtangleHeight = this.mapPanel.Height / network.Height;

            // int maxWinCount = network.getNeurons().Select(o => o.Max(n => n.GetWinCountForClass(classIndex))).Max();
            int maxWinCount = network.getNeurons().Select(o => o.Max(n => n.getWinCount())).Max();
            for (int i = 0; i < network.Width; i++)
            {
                for (int j = 0; j < network.Height; j++)
                {
                    KohenenNeuron neuron = network.getNeurons()[i][j];
                    int winCount;
                    if (classIndex != AnyClassIndex)
                    {
                        if (normalizationWithinClass)
                        {
                            winCount = neuron.GetWinCountForClass(classIndex);
                        }
                        else
                        {
                            winCount = neuron.GetWinCountForClass(classIndex) > 0 ? neuron.getWinCount() : 0;
                        }                        
                    }
                    else
                    {
                        winCount = neuron.getWinCount();                                               
                    }

                    int value = (int)(ColorMaxValue * (double)winCount / maxWinCount);
                    Color color = Color.FromArgb(ColorMaxValue - value, ColorMaxValue - value, ColorMaxValue - value);        

                    SolidBrush brush = new SolidBrush(color);
                    mapGraphics.FillRectangle(brush, i * regtangleWidth, j * regtangleHeight, regtangleWidth, regtangleHeight);                    
                }
            }
        }        

        public class TextBoxLogger : ILogger
        {
            private TextBox textBox;

            public TextBoxLogger(TextBox textBox)
            {
                this.textBox = textBox;
            }

            public void Log(string message)
            {
                textBox.AppendText(message);
                textBox.AppendText(Environment.NewLine);
            }
        }

        private void classesListBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            StateChanched();
        }

        private void checkBox_CheckedChanged(object sender, EventArgs e)
        {
            StateChanched();
        }

        private void StateChanched()
        {
            if (network != null)
            {
                ShowResult(network, this.classesListBox.SelectedIndex, checkBox.Checked);

                int index = this.testResultListBox.SelectedIndex;
                if (index != -1)
                {
                    double[] mfcc = this.mfcc[index];
                    network.recalculateOutput(mfcc);
                    KohenenNeuron neuron = network.getWinner();
                    HighlightNeuron(neuron);
                }
            }
        }

        private void browseFileButton_Click(object sender, EventArgs e)
        {
            DialogResult result = openFileDialog.ShowDialog();
            if (result == DialogResult.OK)
            {
                this.fileToRecognizePathTextBox.Text = openFileDialog.FileName;
            }
        }

        private void recognizeButton_Click(object sender, EventArgs e)
        {
            this.testResultListBox.Items.Clear();

            string fileToRecognize = this.fileToRecognizePathTextBox.Text;
            List<Feature> features = featureExtractor.Extract(fileToRecognize, FeatureConstants.SamplesPerFrame);
            mfcc = featureNormalizer.Normalize(features.Select(o => o.MFCC));

            ShowTestResult(mfcc);
        }

        private void ShowTestResult(List<double[]> mfcc)
        {

            for (int i = 0; i < mfcc.Count; i++)
            {
                StringBuilder text = new StringBuilder();
                double[] values = mfcc[i];
                foreach (double value in values)
                {
                    text.Append(value.ToString("F2"));
                    text.Append(" ");
                }
                testResultListBox.Items.Add(text.ToString());
            }
        }

        private void testResultListBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            StateChanched();         
        }

        private void HighlightNeuron(KohenenNeuron neuron)
        {
            int regtangleWidth = this.mapPanel.Width / network.Width;
            int regtangleHeight = this.mapPanel.Height / network.Height;

            int x = neuron.getX();
            int y = neuron.getY();

            Pen pen = new Pen(Color.Red);

            mapGraphics.DrawRectangle(pen, x * regtangleWidth, y * regtangleHeight, regtangleWidth, regtangleHeight);
        }

        private void fileToRecognizePathTextBox_TextChanged(object sender, EventArgs e)
        {
            this.recognizeButton.Enabled = this.fileToRecognizePathTextBox.Text.Length > 0;
        }
    }
}
